mmu = {
    _rom: "",
    _eram: [],
    _wram: [],
    _zram: [],
    _inbios: 0,

    _romoffs: 0x4000,
    _ramoffs: 0,

    _ie: 0,
    _if: 0,

    _carttype: 0,
    _mbc: [
        {},
        { rombank: 0, rambank: 0, ramon: 0, mode: 0 }
    ],

    load: function (file) {
        b = new BinFileReader(file);
        mmu._rom = b.readString(b.getFileSize(), 0);
        mmu._carttype = mmu._rom.charCodeAt(0x0147);
    },
    /*
    init: function () {
    var i = 0;
    for (i = 0; i < 8192; i++) mmu._wram[i] = 0;
    for (i = 0; i < 32768; i++) mmu._eram[i] = 0;
    for (i = 0; i < 127; i++) mmu._zram[i] = 0;

    mmu._inbios = 0;
    mmu._ie = 0;
    mmu._if = 0;

    mmu._carttype = 0;
    mmu._mbc[0] = {};
    mmu._mbc[1] = { rombank: 0, rambank: 0, ramon: 0, mode: 0 };
    mmu._romoffs = 0x4000;
    mmu._ramoffs = 0;
    },*/
    reset: function () {
        for (i = 0; i < 8192; i++) mmu._wram[i] = 0;
        for (i = 0; i < 32768; i++) mmu._eram[i] = 0;
        for (i = 0; i < 127; i++) mmu._zram[i] = 0;

        mmu._inbios = 1;
        mmu._ie = 0;
        mmu._if = 0;

        mmu._carttype = 0;
        mmu._mbc[0] = {};
        mmu._mbc[1] = { rombank: 0, rambank: 0, ramon: 0, mode: 0 };
        mmu._romoffs = 0x4000;
        mmu._ramoffs = 0;
    },

    rb: function (addr) {
        switch (addr & 0xf000) {
            case 0x0000:
                if (mmu._inbios) {
                    if (addr < 0x0100) return mmu._bios[addr];
                    else if (cpu._r.pc == 0x0100) mmu._inbios = 0;
                    else return mmu._rom.charCodeAt(addr);
                }
            case 0x1000:
            case 0x2000:
            case 0x3000:
                return mmu._rom.charCodeAt(addr);
            case 0x4000:
            case 0x5000:
            case 0x6000:
            case 0x7000:
                return mmu._rom.charCodeAt(mmu._romoffs + (addr & 0x3fff)); //?
            case 0x8000:
            case 0x9000:
                return gpu._vram[addr & 0x1fff]; //?
            case 0xa000:
            case 0xb000:
                return mmu._eram[mmu._ramoffs + (addr & 0x1fff)]; //?
            case 0xc000:
            case 0xd000:
            case 0xe000:
                return mmu._wram[addr & 0x1fff]; //?
            case 0xf000:
                switch (addr & 0x0f00) {
                    case 0x000: case 0x100: case 0x200: case 0x300:
                    case 0x400: case 0x500: case 0x600: case 0x700:
                    case 0x800: case 0x900: case 0xa00: case 0xb00:
                    case 0xc00: case 0xd00:
                        return mmu._wram[addr & 0x1fff]; //?
                    case 0xe00:
                        return ((addr & 0xff) < 0xa0) ? gpu._oam[addr & 0xff] : 0;
                    case 0xf00:
                        if (addr == 0xffff) return mmu._ie;
                        else if (addr > 0xff7f) return mmu._zram[addr & 0x7f];
                        else switch (addr & 0xf0) {
                            case 0x00:
                                switch (addr & 0xf) {
                                    case 0: return key.rb();
                                    case 4: case 6:
                                    case 6: case 7:
                                        return timer.rb(addr);
                                    case 15: return mmu._if;
                                    default: return 0;
                                }
                            case 0x10: case 0x20: case 0x30:
                                return 0;
                            case 0x40: case 0x50:
                            case 0x60: case 0x70:
                                return gpu.rb(addr);
                        }
                }

        }
    },
    rw: function (addr) { return (mmu.rb(addr + 1) << 8) | mmu.rb(addr) },

    wb: function (addr, val) {
        if (addr == 0xcffd) {
            var b = 0;
        }
        switch (addr & 0xf000) {
            case 0x0000:
            case 0x1000:
                if (mmu._carttype == 1) {
                    mmu._mbc[1].ramon = (val & 0xa) ? 1 : 0;
                }
                break;
            case 0x2000:
            case 0x3000:
                if (mmu._carttype == 1) {
                    val &= 0x1f;
                    if (!val) val = 1;
                    mmu._mbc[1].rombank &= 0x60;
                    mmu._mbc[1].rombank |= val;
                    mmu._romoffs = mmu._mbc[1].rombank * 0x4000;
                    break;
                }
            case 0x4000:
            case 0x5000:
                if (mmu._carttype == 1) {
                    if (mmu._mbc[1].mode) {
                        mmu._mbc[1].rambank = (val & 3);
                        mmu._ramoffs = mmu._mbc[1].rambank * 0x2000;
                    }
                    else {
                        mmu._mbc[1].rombank &= 0x1f;
                        mmu._mbc[1].rombank |= ((val & 3) << 5);
                        mmu._romoffs = mmu._mbc[1].rombank * 0x4000;
                    }
                    break;
                }
            case 0x6000:
            case 0x7000:
                if (mmu._carttype == 1) mmu._mbc[1].mode = val & 1; break;
            case 0x8000:
            case 0x9000:
                gpu._vram[addr & 0x1fff] = val;
                gpu.updatetile(addr & 0x1fff, val);
                break;
            case 0xa000:
            case 0xb000:
                mmu._eram[mmu._ramoffs + (addr & 0x1fff)] = val; break;
            case 0xc000:
            case 0xd000:
            case 0xe000:
                if (addr == 53246) {
                    var b = 0;
                }
                mmu._wram[addr & 0x1fff] = val; break;
            case 0xf000:
                switch (addr & 0xf00) {
                    case 0x000: case 0x100: case 0x200: case 0x300:
                    case 0x400: case 0x500: case 0x600: case 0x700:
                    case 0x800: case 0x900: case 0xa00: case 0xb00:
                    case 0xc00: case 0xd00:
                        mmu._wram[addr & 0x1fff] = val; break;
                    case 0xe00:
                        if ((addr & 0xff) < 0xa0) gpu._oam[addr & 0xff] = val;
                        gpu.updateoam(addr, val);
                        break;
                    case 0xf00:
                        if (addr == 0xffff) mmu._ie = val;
                        else if (addr > 0xff7f) mmu._zram[addr & 0x7f] = val;
                        else switch (addr & 0xf0) {
                            case 0x00:
                                switch (addr & 0xf) {
                                    case 0: key.wb(val); break;
                                    case 4: case 5:
                                    case 6: case 7:
                                        timer.wb(addr, val); break;
                                    case 15: mmu._if = val; break;
                                }
                            case 0x10: case 0x20: case 0x30:
                                break;
                            case 0x40: case 0x50:
                            case 0x60: case 0x70:
                                gpu.wb(addr, val); break;
                        }
                        break;
                }
                break;

        }
    },
    ww: function (addr, val) {
        mmu.wb(addr, val & 0xff);
        mmu.wb(addr + 1, val >> 8);
    },
    _bios: [
        0x31, 0xFE, 0xFF, 0xAF, 0x21, 0xFF, 0x9F, 0x32, 0xCB, 0x7C, 0x20, 0xFB, 0x21, 0x26, 0xFF, 0x0E,
        0x11, 0x3E, 0x80, 0x32, 0xE2, 0x0C, 0x3E, 0xF3, 0xE2, 0x32, 0x3E, 0x77, 0x77, 0x3E, 0xFC, 0xE0,
        0x47, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x1A, 0xCD, 0x95, 0x00, 0xCD, 0x96, 0x00, 0x13, 0x7B,
        0xFE, 0x34, 0x20, 0xF3, 0x11, 0xD8, 0x00, 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9,
        0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20,
        0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0x67, 0x3E, 0x64, 0x57, 0xE0, 0x42, 0x3E, 0x91, 0xE0, 0x40, 0x04,
        0x1E, 0x02, 0x0E, 0x0C, 0xF0, 0x44, 0xFE, 0x90, 0x20, 0xFA, 0x0D, 0x20, 0xF7, 0x1D, 0x20, 0xF2,
        0x0E, 0x13, 0x24, 0x7C, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x06,
        0x7B, 0xE2, 0x0C, 0x3E, 0x87, 0xF2, 0xF0, 0x42, 0x90, 0xE0, 0x42, 0x15, 0x20, 0xD2, 0x05, 0x20,
        0x4F, 0x16, 0x20, 0x18, 0xCB, 0x4F, 0x06, 0x04, 0xC5, 0xCB, 0x11, 0x17, 0xC1, 0xCB, 0x11, 0x17,
        0x05, 0x20, 0xF5, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B,
        0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E,
        0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC,
        0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, 0x3c, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x4C,
        0x21, 0x04, 0x01, 0x11, 0xA8, 0x00, 0x1A, 0x13, 0xBE, 0x20, 0xFE, 0x23, 0x7D, 0xFE, 0x34, 0x20,
        0xF5, 0x06, 0x19, 0x78, 0x86, 0x23, 0x05, 0x20, 0xFB, 0x86, 0x20, 0xFE, 0x3E, 0x01, 0xE0, 0x50
    ]
}